// Copyright 2023 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "logging.h"
#include "HWSecure/include/securec.h"
#ifdef __cplusplus
#include <cstdarg>
#include <cstdio>
#include <cstring>
#include <libgen.h>
#include <string>
#else
#include <libgen.h>
#include <stdarg.h>
#include <stdio.h>
#include <string>
#include <string.h>
#endif
#ifdef __ANDROID__
#include "log/log.h"
#endif

#ifndef __ANDROID__
int g_logLevelMap[] = {
    0,                // ANDROID_LOG_UNKNOWN
    0,                // ANDROID_LOG_DEFAULT
    0,                // ANDROID_LOG_VERBOSE
    LOG_DEBUG,        // ANDROID_LOG_DEBUG
    LOG_INFO,         // ANDROID_LOG_INFO
    LOG_WARNING,      // ANDROID_LOG_WARN
    LOG_ERR,          // ANDROID_LOG_ERROR
    LOG_CRIT,         // ANDROID_LOG_FATAL
    0                 // ANDROID_LOG_SILENT
};

LogCallbackFunction g_logCallback = nullptr;

#endif

// default is ANDROID_LOG_INFO
int g_logLevel = ANDROID_LOG_INFO;
std::string g_logFileName = "/data/mediaEngine/libCPHMediaEngine.log";

// log
int logNum = 0;
int logFileSize = 0;
std::ofstream logout;
const int timeLength = 128;
const int errMaxSize = 100 * 1024 *1024;
const std::string debug = "DEBUG";
const std::string info = "INFO";
const std::string warn = "WARN";
const std::string error = "ERROR";
const std::string fatal = "FATAL";
const std::string unknow = "UNKNOW";

/**
 * @brief: set log level
 * @param [in] level: log level, default is ANDROID_LOG_INFO
 */
void SetLogLevel(int level)
{
    if (level == ANDROID_LOG_DEBUG || level == ANDROID_LOG_INFO
        || level == ANDROID_LOG_ERROR || level == ANDROID_LOG_FATAL) {
        g_logLevel = level;
        return;
    }
}

/**
 * @brief: set log file name
 * @param [in] fileName: log file name
 */
void SetLogFileName(const char* logFileName)
{
    g_logFileName = logFileName;
}

/**
 * @brief: get log level
 */
const char* GetLevelString(const int level) {
    const char* logLevel;
    switch (level) {
        case ANDROID_LOG_DEBUG :
            logLevel = debug.c_str();
            break;
        case ANDROID_LOG_INFO :
            logLevel = info.c_str();
            break;
        case ANDROID_LOG_WARN :
            logLevel = warn.c_str();
            break;
        case ANDROID_LOG_ERROR :
            logLevel = error.c_str();
            break;
        case ANDROID_LOG_FATAL :
            logLevel = fatal.c_str();
            break;
        default :
            logLevel = unknow.c_str();
            break;
    }
    return logLevel;
}

/**
 * @brief: get file size
 */
void GetFileSize()
{
    struct stat fileStat;
    if (stat(g_logFileName.c_str(), &fileStat) != 0) {
        return;
    }
    logFileSize = fileStat.st_size;
}

/**
 * @brief: write log to file[/data/libCPHMeidaEngine.log]
 */
void HandleCPHLog(const int level, const char *tag, const char *log)
{
    if (logFileSize > errMaxSize) {
        return;
    }

    const char* logLevel = GetLevelString(level);
    int logLength = strlen(log) + timeLength;
    char logTarget[logLength];
    (void)memset_s(logTarget, logLength, 0, logLength);
    struct timeval tv;
    (void)gettimeofday(&tv,NULL);
    struct tm *lTm = localtime(&tv.tv_sec);

    if (logNum > 1000) {
        GetFileSize();
        logNum = 0;
    }
    if (!logout.is_open()) {
        logout.open(g_logFileName.c_str(), std::ofstream::binary | std::ofstream::app | std::ofstream::out);
    }

    if (logout.is_open()) {
#ifdef __ANDROID__
        (void)sprintf_s(logTarget, sizeof(logTarget),"[%02d-%02d %02d:%02d:%02d.%03d] [%d %d] %s %s %s" , \
               (lTm->tm_mon + 1), \
               lTm->tm_mday, \
               lTm->tm_hour, \
               lTm->tm_min, \
               lTm->tm_sec, \
               tv.tv_usec/1000,\
               (int)getpid(), (int)gettid(), \
               logLevel, \
               tag, \
                log);
#else
        (void)sprintf_s(logTarget, sizeof(logTarget),"[%02d-%02d %02d:%02d:%02d.%03d] [%d] %s %s %s" , \
               (lTm->tm_mon + 1), \
               lTm->tm_mday, \
               lTm->tm_hour, \
               lTm->tm_min, \
               lTm->tm_sec, \
               tv.tv_usec/1000,\
               (int)getpid(), \
               logLevel, \
               tag, \
                log);
#endif
        logout.write(logTarget, strlen(logTarget));
        logout.write("\n",strlen("\n"));
        logout.flush();
        logNum++;
    }
}


/**
 * @brief: VmiLogPrint, don't use it directly, use macro's instead
 * @param [in] fileName: code file anem
 * @param [in] lineNumber: code line number
 * @param [in] functionName: code function name
 * @param [in] level: print level
 * @param [in] vmiTag: android log tag, Nullptr allowed
 * @param fmt : as fmt in printf
 * @param ... : variable number of arguments
 */
void VmiLogPrint(const char* fileName, const int lineNumber, const char* functionName, const int level,
                 const char* vmiTag, const char* fmt, ...)
{
    if (fileName == nullptr || functionName == nullptr || fmt == nullptr) {
        return;
    }
    const size_t logBufSize = 512;
    if (level < g_logLevel) {
        return;
    }

    // this is a NULL that maybe passed by android system header file log.h
    // don't change this to nullptr
    // LOG_TAG define NULL in system/core/include/log/log.h
    if (vmiTag == NULL) {
        vmiTag = "Native";
    }
    std::string tag = "VMI_" + std::string(vmiTag);

    char szBuff[logBufSize] = {0};
    int printedSize = 0;

    const char* fileBaseName = nullptr;
    auto newFileName = strdup(fileName);
    if (newFileName != nullptr) {
        fileBaseName = basename(newFileName);
    } else {
        fileBaseName = "Unknown";
    }
    printedSize += snprintf_s(szBuff, logBufSize - 1, logBufSize - 1, "[%s:%d]%s -> ", fileBaseName,
                              lineNumber, functionName);
    /* First release the memory of newfilename, and then judge the return value of snprintf */
    if (newFileName != nullptr) {
        free(newFileName);
        newFileName = nullptr;
    }
    if (printedSize <= 0) {
        return;
    }

    va_list ap;
    va_start(ap, fmt);
    int ret = vsnprintf_s(szBuff + printedSize, logBufSize - 1 - printedSize,
        logBufSize - 1 - printedSize, fmt, ap);
    va_end(ap);

    if (ret <= 0) {
        return;
    }
    printedSize += ret;

    if (static_cast<int64_t>(printedSize) < static_cast<int64_t>(logBufSize)) {
        szBuff[printedSize] = '\0';
    }
#ifdef __ANDROID__
    //(void)__android_log_write(level, tag.c_str(), szBuff);
    (void)HandleCPHLog(level, tag.c_str(), szBuff);
#else
    if (g_logCallback != nullptr) {
        g_logCallback(level, tag.c_str(), szBuff);
    } else {
        openlog("", LOG_PID, 0);
        syslog(g_logLevelMap[level] | LOG_USER, "%s", szBuff);
    }
#endif
}

/**
 * @brief: print buffer in hexidecimal format, for debug only,
 * Log level uses FATAL to ensure output regardless of log level
 * @param [in] title: name of the buffer
 * @param [in] buffer: buffer pointer
 * @param [in] length: buffer length
 */
void HexDump(const char* title, const uint8_t* buffer, const int byteLength)
{
    if (buffer == nullptr || byteLength <= 0) {
        FATAL("Illegal input parameter, buffer[%p] byteLength[%d]", buffer, byteLength);
        return;
    }
    if (title != nullptr) {
        FATAL("dumpping:%s", title);
    }
    const int maxLineCharacters = 10;
    const int maxByteCharacters = 4;
    const int numBytesPerLine = 16;

    char value[maxLineCharacters];
    std::string output;
    int ret = 0;
    for (int i = 0; i < byteLength; i++) {
        if (i % numBytesPerLine == 0) {
            if (output.length() > 0) {
                FATAL("%s", output.c_str());
            }
            output.clear();
            ret = snprintf_s(&value[0], maxLineCharacters, maxLineCharacters - 1, "%08x:", i / numBytesPerLine);
            if (ret > 0) {
                output += value;
            } else {
                FATAL("Failed to sprintf_s in HexDump");
            }
        }
        ret = snprintf_s(&value[0], maxLineCharacters, maxByteCharacters - 1, "%02x ", buffer[i]);
        if (ret > 0) {
            output += value;
        } else {
            FATAL("Failed to sprintf_s in HexDump");
        }

    }

    if (output.length() > 0) {
        FATAL("%s", output.c_str());
    }
}

bool VmiIsLoggable(int level, const char* vmiTag, int defaultLevel)
{
#ifdef __ANDROID__
    if (vmiTag != nullptr) {
        return __android_log_is_loggable(level, vmiTag, defaultLevel);
    }
#endif
    return true;
}

#ifndef __ANDROID__

/**
 * @brief: set log callback funciton pointer
 *         Note:Multithreaded calls are not supported
 * @param [in] callback: log callback function pointer
 *        if callback is nullptr, then revet to printing with syslog
 */
void SetLogCallback(LogCallbackFunction callback)
{
    g_logCallback = callback;
}

#endif
